package io.gitee.zhangbinhub.admin.log.service

import com.mybatisflex.core.query.QueryChain
import com.mybatisflex.core.query.QueryMethods
import com.mybatisflex.core.query.QueryWrapper
import com.mybatisflex.core.util.LambdaUtil
import io.gitee.zhangbinhub.acp.boot.exceptions.WebException
import io.gitee.zhangbinhub.acp.boot.log.LogAdapter
import io.gitee.zhangbinhub.acp.core.common.CalendarTools
import io.gitee.zhangbinhub.acp.core.common.CommonTools
import io.gitee.zhangbinhub.admin.common.vo.CustomerQueryPageVo
import io.gitee.zhangbinhub.admin.log.base.BaseLogEntity
import io.gitee.zhangbinhub.admin.log.base.BaseService
import io.gitee.zhangbinhub.admin.log.entity.LoginLog
import io.gitee.zhangbinhub.admin.log.entity.LoginLogHistory
import io.gitee.zhangbinhub.admin.log.entity.OperateLog
import io.gitee.zhangbinhub.admin.log.entity.RouteLog
import io.gitee.zhangbinhub.admin.log.po.LogQueryPo
import io.gitee.zhangbinhub.admin.log.repo.*
import io.gitee.zhangbinhub.admin.log.vo.LoginLogVo
import io.gitee.zhangbinhub.admin.resource.server.nami.client.CommonOauthServer
import io.gitee.zhangbinhub.admin.resource.server.tools.TokenInfoTools
import io.gitee.zhangbinhub.admin.resource.server.vo.ApplicationVo
import io.gitee.zhangbinhub.admin.resource.server.vo.TokenUserInfoVo
import org.noear.snack4.ONode
import org.noear.solon.annotation.Component
import org.noear.solon.data.annotation.Transaction

/**
 * @since JDK 11
 */
@Component
open class LogService(
    private val logAdapter: LogAdapter,
    private val routeLogRepository: RouteLogRepository,
    private val routeLogHistoryRepository: RouteLogHistoryRepository,
    private val operateLogRepository: OperateLogRepository,
    private val operateLogHistoryRepository: OperateLogHistoryRepository,
    private val loginLogRepository: LoginLogRepository,
    private val loginLogHistoryRepository: LoginLogHistoryRepository,
    private val commonOauthServer: CommonOauthServer
) : BaseService() {
    /**
     * 路由消息转为对应的实体对象
     */
    @Throws(WebException::class)
    private fun <T> messageToEntity(message: String, cls: Class<T>): T {
        return ONode.deserialize(message, cls) ?: throw WebException("日志消息转换失败")
    }

    private fun getTokenInfo(token: String): TokenUserInfoVo = TokenInfoTools.getUserInfoFromToken(token)

    private fun getAppInfo(token: String): ApplicationVo = try {
        commonOauthServer.appInfo(token)
    } catch (e: Exception) {
        ApplicationVo()
    }

    @Transaction
    fun doRouteLog(message: String) {
        val routeLog = messageToEntity(message, RouteLog::class.java)
        if (routeLog.token != null) {
            try {
                getAppInfo(routeLog.token!!).let { app ->
                    routeLog.clientId = app.id
                    routeLog.clientName = app.appName
                    routeLog.identify = app.identify
                    if (!CommonTools.isNullStr(app.id)) {
                        getTokenInfo(routeLog.token!!).let {
                            routeLog.userId = it.id.toString()
                            routeLog.loginNo = it.loginNo.toString()
                            routeLog.userName = it.name.toString()
                        }
                    }
                }
            } catch (e: Exception) {
                logAdapter.error(e.message, e)
            }
        }
        routeLogRepository.insert(routeLog)
    }

    @Transaction
    fun doOperateLog(message: String) {
        try {
            val operateLog = messageToEntity(message, OperateLog::class.java)
            getAppInfo(operateLog.token!!).let { app ->
                operateLog.clientId = app.id
                operateLog.clientName = app.appName
                operateLog.identify = app.identify
                if (!CommonTools.isNullStr(app.id)) {
                    getTokenInfo(operateLog.token!!).let {
                        operateLog.userId = it.id.toString()
                        operateLog.loginNo = it.loginNo.toString()
                        operateLog.userName = it.name.toString()
                    }
                    operateLogRepository.insert(operateLog)
                }
            }
        } catch (e: Exception) {
            logAdapter.error(e.message, e)
            throw WebException("操作日志记录失败")
        }
    }

    @Transaction
    fun doLoginLog(message: String) {
        try {
            val loginLog = messageToEntity(message, LoginLog::class.java)
            getAppInfo(loginLog.token!!).let { app ->
                if (!CommonTools.isNullStr(app.id)) {
                    loginLog.clientId = app.id
                    loginLog.clientName = app.appName
                    loginLog.identify = app.identify
                    getTokenInfo(loginLog.token!!).let {
                        loginLog.userId = it.id.toString()
                        loginLog.loginNo = it.loginNo.toString()
                        loginLog.userName = it.name.toString()
                        loginLog.loginDate = CalendarTools.getDateTimeString(
                            CalendarTools.getCalendar(loginLog.requestTime),
                            CalendarTools.DATE_FORMAT
                        )
                        loginLogRepository.insert(loginLog)
                    }
                }
            }
        } catch (e: Exception) {
            logAdapter.error(e.message, e)
            throw WebException("登录日志记录失败")
        }
    }

    fun loginStatistics(beginTime: Long): List<LoginLogVo> =
        loginLogHistoryRepository.selectListByQueryAs(
            QueryWrapper.create()
                .select(LambdaUtil.getQueryColumn(LoginLogHistory::getClientId).`as`(LoginLogVo::appId.name))
                .select(LambdaUtil.getQueryColumn(LoginLogHistory::getClientName).`as`(LoginLogVo::appName.name))
                .select(LambdaUtil.getQueryColumn(LoginLogHistory::loginDate).`as`(LoginLogVo::date.name))
                .select(QueryMethods.count(LoginLogHistory::id).`as`(LoginLogVo::count.name))
                .where(LoginLogHistory::getRequestTime).ge(beginTime)
                .groupBy(LoginLogHistory::getClientId, LoginLogHistory::getClientName, LoginLogHistory::loginDate),
            LoginLogVo::class.java
        )

    private inline fun <reified T> doPageQuery(
        queryChain: QueryChain<T>,
        logQueryPo: LogQueryPo
    ): CustomerQueryPageVo<T> =
        queryChain.where(BaseLogEntity::getRemoteIp)
            .like(logQueryPo.remoteIp, !CommonTools.isNullStr(logQueryPo.remoteIp))
            .and(BaseLogEntity::getGatewayIp).like(logQueryPo.gatewayIp, !CommonTools.isNullStr(logQueryPo.gatewayIp))
            .and(BaseLogEntity::getPath).like(logQueryPo.path, !CommonTools.isNullStr(logQueryPo.path))
            .and(BaseLogEntity::getServerId).like(logQueryPo.serverId, !CommonTools.isNullStr(logQueryPo.serverId))
            .and(BaseLogEntity::getClientName)
            .like(logQueryPo.clientName, !CommonTools.isNullStr(logQueryPo.clientName))
            .and(BaseLogEntity::getUserName).like(logQueryPo.userName, !CommonTools.isNullStr(logQueryPo.userName))
            .and(BaseLogEntity::getRequestTime).ge(logQueryPo.startTime, logQueryPo.startTime != null)
            .and(BaseLogEntity::getRequestTime).le(logQueryPo.endTime, logQueryPo.endTime != null)
            .and(BaseLogEntity::getResponseStatus).eq(logQueryPo.responseStatus, logQueryPo.responseStatus != null)
            .let { queryChain ->
                doPageQuery(logQueryPo.queryParam!!, queryChain)
            }

    fun doQueryRouteLog(logQueryPo: LogQueryPo): CustomerQueryPageVo<out BaseLogEntity> =
        logQueryPo.let {
            if (it.history) {
                routeLogHistoryRepository
            } else {
                routeLogRepository
            }
        }.let {
            doPageQuery(QueryChain.of(it), logQueryPo)
        }

    fun doQueryOperateLog(logQueryPo: LogQueryPo): CustomerQueryPageVo<out BaseLogEntity> =
        logQueryPo.let {
            if (it.history) {
                operateLogHistoryRepository
            } else {
                operateLogRepository
            }
        }.let {
            doPageQuery(QueryChain.of(it), logQueryPo)
        }

    fun doQueryLoginLog(logQueryPo: LogQueryPo): CustomerQueryPageVo<out BaseLogEntity> =
        logQueryPo.let {
            if (it.history) {
                loginLogHistoryRepository
            } else {
                loginLogRepository
            }
        }.let {
            doPageQuery(QueryChain.of(it), logQueryPo)
        }

}
